use sea_orm::DbErr;
use thiserror::Error;
use std::{env::VarError, string::FromUtf8Error};

use super::web_ret::{EmptyData, WebRet};

#[allow(dead_code)]
#[derive(Debug, Error)]
pub enum AppError {
    #[error("Unable to read .env {error:?}: {key}")]
    EnvFail{error:VarError, key:String},

    #[error("ID may not be less than 10, but it was {id}")]
    InvalidId{id:u16},

    #[error("Empty to read .env {key}")]
    EnvEmpty { key: String },
    //
    #[error("code:{code} ,desc:{desc}")]
    BizError { code: i32, desc: String },
}

#[allow(dead_code)]
impl AppError {
    pub fn to_box(self) ->Box<AppError>{
        Box::new(self)
    }
    pub fn new_app(code: i32, desc: String) -> AppError {
        AppError::BizError {
            code: code,
            desc: desc,
        }
    }
    pub fn new_app_redis(error: r2d2_redis::r2d2::Error) -> AppError {
        let ec = ErrorCode::SYSTEM_ERROR_REDIS;
        AppError::BizError {
            code: ec.code,
            desc: format!("{}:{}", ec.desc, error),
        }
    }
    pub fn new_app_redis_cmd(error: r2d2_redis::redis::RedisError) -> AppError {
        let ec = ErrorCode::SYSTEM_ERROR_REDIS;
        AppError::BizError {
            code: ec.code,
            desc: format!("{}:{}", ec.desc, error),
        }
    }
    pub fn new_serde_json(error: serde_json::Error) -> AppError {
        let ec = ErrorCode::SYSTEM_ERROR_PARSE;
        AppError::BizError {
            code: ec.code,
            desc: format!("{}:{}", ec.desc, error),
        }
    }
    pub fn new_db_json(error: DbErr) -> AppError {
        let ec = ErrorCode::SYSTEM_ERROR_DB;
        AppError::BizError {
            code: ec.code,
            desc: format!("{}:{}", ec.desc, error),
        }
    }
    pub fn new_utf8_json(error: FromUtf8Error) -> AppError {
        let ec = ErrorCode::SYSTEM_ERROR_DB;
        AppError::BizError {
            code: ec.code,
            desc: format!("{}:{}", ec.desc, error),
        }
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ErrorCode {
    code: i32,
    desc: &'static str,
}

impl ErrorCode {
    pub fn get_code(&self) -> i32 {
        self.code
    }
    pub fn get_desc(&self) -> &'static str {
        self.desc
    }
    pub fn to_web_ret(&self) -> WebRet<EmptyData> {
        WebRet {
            code: self.code,
            msg: self.desc.to_string(),
            data: None,
        }
    }
}
macro_rules! error_code {
    (
        $(
            $(#[$docs:meta])*
            ($name:ident,$code:expr,$desc:expr),
        )+
    ) => {
        impl ErrorCode{
            $(
                $(#[$docs])*
                pub const $name:ErrorCode = ErrorCode{
                    code:$code,
                    desc:$desc,
                };
            )+
            #[allow(dead_code)]
            pub fn match_error(code:i32)->Option<ErrorCode>{
                match code{
                    $(
                        $code => Some(ErrorCode::$name),
                    )+
                    _=>None
                }
            }
        }
    };
}

error_code! {
    // http error
    (SUCCESS,00,"success"),
    (BAD_REQUEST,400,"bad_request"),
    (UNAUTH,401,"unauth"),
    (INTERNAL_ERROR,500,"internal_error"),
    (TIMEOUT,408,"timeout"),
    (NOT_FOUND,404,"not_found"),
    // app system error
    (SYSTEM_ERROR_REDIS,10001,"system_error_redis"),
    (SYSTEM_ERROR_DB,10002,"system_error_db"),
    (SYSTEM_ERROR_PARSE,10003,"system_error_parse"),
    (SYSTEM_ERROR_CODE,10004,"system_error_code"),
    (BIZ_ERROR_COMMON,40001,"biz_error_common"),
}
